﻿#include "userinterface.h"

using namespace aviation;
using namespace std;

UserInterface::UserInterface(ReservationSystem& reservationSystem)
	:
m_rs(reservationSystem),
m_accessLevel(UNDETERMINED)
{
}

UserInterface::~UserInterface()
{
}

void UserInterface::Run()
{
	bool quit = false;

	do
	{
		m_accessLevel = LoginDialogue();
	}
	while(m_accessLevel == UNDETERMINED);

	while(quit == false)
	{
		ClearScreen();
		PrintTitle("Airline Reservation System - Main Menu");

		cout 
		<< "              -=\\`\\   What would you like to do?\n"
"          |\\ ____\\_\\__       \n" 
"        -=\\c`\"\"\"\"\"\"\" \"`)     1. Add a customer\n"
"           `~~~~~/ /~~`      2. Make a customer inquiry\n"
"             -==/ /          3. Make a flight inquiry\n"
"               '-'           4. Add a reservation\n"
"			     5. Cancel a reservation\n"
"               _  _\n";
cout << "              ( `   )_"; if(m_accessLevel == ADMINISTRATOR) cout << "       6. Add a user"; cout << "\n";
cout << "             (    )    `)"; if(m_accessLevel == ADMINISTRATOR) cout << "    7. Remove a user"; cout << "\n";
cout << "           (_   (_ .  _) _)"; if(m_accessLevel == ADMINISTRATOR) cout << "  8. Add a flight"; cout << "\n";
cout << "                             "; if(m_accessLevel == ADMINISTRATOR) cout << "9. Remove a flight"; cout << "\n";
cout << "\n                                         (  )\n"
"          _ .                         ( `  ) . )\n"
"        (  _ )_       0. Exit        (_, _(  ,_)_)\n"
"      (_  _(_ ,)";

		bool admin = m_accessLevel == ADMINISTRATOR;

		switch(GetCh())
		{
		case '0':
			quit = true;
			break;
		case '1':
			AddCustomerDialogue();
			break;
		case '2':
			CustomerInquiryDialogue();
			break;
		case '3':
			FlightInquiryDialogue();
			break;
		case '4':
			AddReservationDialogue();
			break;
		case '5':
			CancelReservationDialogue();
			break;
		case '6':
			if(admin) { AddUserDialogue(); break; }
		case '7':
			if(admin) { RemoveUserDialogue(); break; }
		case '8':
			if(admin) { AddFlightDialogue(); break; }
		case '9':
			if(admin) { RemoveFlightDialogue(); break; }
		default:
			Alert("Error", "Invalid option. Please try again.");
			break;
		}
	}
}

UserInterface::ACCESS_LEVEL UserInterface::LoginDialogue()
{
	String strLogin;
	String strPass;

	ClearScreen();

	PrintTitle("Login Menu");

	strLogin = GetUserLine("Enter username:");

	auto foundUser = std::find_if(
		m_rs.userDB.Begin(), 
		m_rs.userDB.End(), 
		[&strLogin](const User& u){return u.GetUsername() == strLogin;});

	if(foundUser == m_rs.userDB.End())
	{
		Alert("Error", "The specified user was not found in the user database.");
		return UNDETERMINED;
	}

	strPass = GetUserLine("Enter password:");

	if(foundUser->GetHashedPassword() != util::HashString(strPass + strLogin))
	{
		Alert("Error", "Incorrect password.");
		return UNDETERMINED;
	}

	m_currentUser = *foundUser;
	return foundUser->IsAdmin() ? ADMINISTRATOR : USER;
}

void UserInterface::AddCustomerDialogue()
{
	do
	{
		try
		{
			ClearScreen();
			PrintTitle("Add Customer");

			String title = GetUserLine("Enter the customer's title.");
			String fName = GetUserLine("Enter the customer's first name.");
			String lName = GetUserLine("Enter the customer's last name.");
			String address = GetUserLine("Enter the customer's address.");
			String passportNumber = GetUserLine("Enter the customer's passport number. [9 numbers or capital letters]");
			String phoneNumber = GetUserLine("Enter the customer's telephone number. [numbers only]");

			Customer tentativeCustomer(title, fName, lName, address, passportNumber, phoneNumber);

			if(m_rs.customerDB.ContainsData(tentativeCustomer)) throw exception("That customer already exists in the database. Nothing has been changed.");
			else m_rs.customerDB.AddData(tentativeCustomer);

			Alert("Success!", "Customer was successfully added to the database.\n\nNew record:\n\n" + PrettyPrintCustomer(tentativeCustomer));
			return;
		}
		catch(exception e)
		{
			Alert("An error has occurred", "Error: '" + String(e.what()) + "'. The customer database has not been modified.");
		}
	}
	while(WouldYouLikeToTryAgain("Would you like to try again?", "Would you like to try again?\nPress 1 to continue or any other key to return to the main menu.\n"));
}

void UserInterface::CustomerInquiryDialogue()
{	
	do
	{
		try
		{
			String message;
			String details;

			const Customer foundCustomer = GetUniqueCustomerFromUserSearch();
	
			auto customerReservations = FindAllMatching(
				m_rs.reservationDB.Begin(),
				m_rs.reservationDB.End(),
				[&foundCustomer](const Reservation& reservation) {return reservation.GetCustomerID() == foundCustomer.GetID();});

			for_each(
				customerReservations.begin(), 
				customerReservations.end(), 
				[this, &details](Database<Reservation>::Iterator rIt) 
				{
					auto customerAndFlight = GetCustomerAndFlightFromReservation(*rIt);
					details += PrettyPrintFlight(customerAndFlight.second) + "\n";
					details += "The customer is " + PrettyPrintReservationStatus(rIt->GetReservationStatus()) + "\n";
				});

			message = "Customer '" + foundCustomer.GetTitle() 
				+ " " + foundCustomer.GetFirstName() 
				+ " " + foundCustomer.GetLastName()
				+ "' is reserved on " + util::XtoA(customerReservations.size()) 
				+ " flight(s)";

			if(customerReservations.size())	message += ":\n\n" + details;
			else message += ".";

			Alert("Inquiry Complete", message);
			return;
		}
		catch(exception e)
		{
			Alert("An error has occurred", String(e.what()));
		}
	}
	while(WouldYouLikeToTryAgain("Would you like to make another inquiry?", "Would you like to make another inquiry?\nPress 1 to continue or any other key to return to the main menu.\n"));
}

void UserInterface::FlightInquiryDialogue()
{
	do
	{
		try
		{
			String message;
			String details;

			const Flight foundFlight = GetUniqueFlightFromUserSearch();
	
			auto flightReservations = FindAllMatching(
				m_rs.reservationDB.Begin(),
				m_rs.reservationDB.End(),
				[&foundFlight](const Reservation& reservation) {return reservation.GetFlightID() == foundFlight.GetID();});

			unsigned numStandardSeats = foundFlight.GetMaxStandardSeats();
			unsigned numFirstSeats = foundFlight.GetMaxFirstSeats();
			unsigned waitingStandard = 0;
			unsigned waitingFirst = 0;

			for_each(
				flightReservations.begin(), 
				flightReservations.end(), 
				[&](Database<Reservation>::Iterator rIt) 
				{
					Customer c = GetCustomerAndFlightFromReservation(*rIt).first;
					details += c.GetTitle() + " " + c.GetFirstName() + " " + c.GetLastName() + ", " + PrettyPrintReservationStatus(rIt->GetReservationStatus()) + "\n";
					switch(rIt->GetReservationStatus())
					{
					case Reservation::FIRST_CLASS:
						--numFirstSeats;
						break;
					case Reservation::STANDARD_CLASS:
						--numStandardSeats;
						break;
					case Reservation::WAITING_FIRST:
						++waitingFirst;
						break;
					case Reservation::WAITING_STANDARD:
						++waitingStandard;
						break;
					default:
						throw exception("Invalid reservation class.");
					}
				});

			if(numStandardSeats > 0) 
				message += "This flight has " + util::XtoA(numStandardSeats) + " standard seats available.\n";
			else
				message += "This flight has a waiting list of " + util::XtoA(waitingStandard) + " people for standard class.\n";

			if(numFirstSeats > 0) 
				message += "This flight has " + util::XtoA(numFirstSeats) + " first class seats available.\n";
			else
				message += "This flight has a waiting list of " + util::XtoA(waitingFirst) + " people for first class.\n";

			message += "This flight has " + util::XtoA(flightReservations.size()) + " associated reservation(s)";

			if(flightReservations.size()) message += ":\n\n" + details;
			else message += ".";

			Alert("Inquiry Complete", message);
			return;
		}
		catch(exception e)
		{
			Alert("An error has occurred", String(e.what()));
		}
	}
	while(WouldYouLikeToTryAgain("Would you like to make another inquiry?", "Would you like to make another inquiry?\nPress 1 to continue or any other key to return to the main menu.\n"));
}

void UserInterface::AddReservationDialogue()
{
	do
	{
		try
		{
			String message, details;
			unsigned standardCount = 0, firstCount = 0;
			bool alreadyReserved = false;

			const Customer foundCustomer = GetUniqueCustomerFromUserSearch();
			const Flight foundFlight = GetUniqueFlightFromUserSearch();

			for_each(
				m_rs.reservationDB.Begin(),
				m_rs.reservationDB.End(),
				[&](const Reservation& ri)
				{
					if(ri.GetFlightID() == foundFlight.GetID())
					{
						if(ri.GetCustomerID() == foundCustomer.GetID()) alreadyReserved = true;

						if(ri.GetReservationStatus() == Reservation::STANDARD_CLASS) ++standardCount;
						else if(ri.GetReservationStatus()  == Reservation::FIRST_CLASS) ++firstCount;
					}
				});

			if(alreadyReserved) throw exception("That customer already has a reservation on that flight.");

			bool firstFull = firstCount == foundFlight.GetMaxFirstSeats();
			bool standardFull = standardCount == foundFlight.GetMaxStandardSeats();
	
			while(1)
			{
				ClearScreen();
				PrintTitle("Select a reservation type");

				firstFull ? 
					cout << "First class is full.\n"
					: cout << "There are " + util::XtoA(foundFlight.GetMaxFirstSeats() - firstCount) + " seats available in First Class.\n";

				standardFull ? 
					cout << "Standard class is full.\n\n"
					: cout << "There are " + util::XtoA(foundFlight.GetMaxStandardSeats() - standardCount) + " seats available in Standard Class.\n\n";

				firstFull ?
					cout << "1. Add customer to the waiting list for First Class.\n" 
					: cout << "1. Reserve customer a First Class seat.\n";

				standardFull ?
					cout << "2. Add customer to the waiting list for Standard Class.\n" 
					: cout << "2. Reserve customer a Standard Class seat.\n";

				cout << "3. Quit dialogue.\n";

				switch(GetCh())
				{
					case '1':
						m_rs.reservationDB.AddData(Reservation(foundFlight, foundCustomer, firstFull ? Reservation::WAITING_FIRST : Reservation::FIRST_CLASS));
						Alert("Reservation Complete", "Successfully added the reservation to the database.");
						return;
					case '2':
						m_rs.reservationDB.AddData(Reservation(foundFlight, foundCustomer, standardFull ? Reservation::WAITING_STANDARD : Reservation::STANDARD_CLASS));
						Alert("Reservation Complete", "Successfully added the reservation to the database.");
						return;
					case '3':
						Alert("Quitting", "Quitting the dialogue. No changes have been made.");
						return;
					default:
						throw(exception("Invalid option."));
				}
			}
		}
		catch(exception e)
		{
			Alert("An error has occurred", String(e.what()));
		}
	}
	while(WouldYouLikeToTryAgain("Would you like to make another inquiry?", 
		"Would you like to make another reservation?\nPress 1 to continue or any other key to return to the main menu.\n"));
}

void UserInterface::CancelReservationDialogue()
{
	do
	{
		try
		{
			const Customer foundCustomer = GetUniqueCustomerFromUserSearch();
			const Flight foundFlight = GetUniqueFlightFromUserSearch();

			auto matchingReservations = FindAllMatching(
				m_rs.reservationDB.Begin(), 
				m_rs.reservationDB.End(),
				[&](const Reservation& r) {	return foundCustomer.GetID() == r.GetCustomerID() && foundFlight.GetID() == r.GetFlightID();});

			if(matchingReservations.size() > 1) throw exception("Found a customer reserved on the same flight more than once.");
			if(matchingReservations.size() == 0) throw exception("Could not find a reservation that matched the customer and flight.");
			else
			{
				if(WouldYouLikeToTryAgain("Are you sure you want to delete this reservation?", 
					"Are you sure you want to delete this reservation?\nPress 1 to continue or any other key to return to the main menu."))
				{
					m_rs.reservationDB.RemoveData(*matchingReservations.back());
					Alert("Cancellation complete", "The customer's reservation has been cancelled.");
					return;
				}
			}
		}
		catch(exception e)
		{
			Alert("An error has occurred", String(e.what()));
		}
	}
	while(WouldYouLikeToTryAgain("Would you like to make another inquiry?",
		"Would you like to make try again?\nPress 1 to continue or any other key to return to the main menu.\n"));
}

void UserInterface::AddUserDialogue()
{
	do
	{
		try
		{
			String username, password;

			ClearScreen();
			PrintTitle("Add User");

			username = GetUserLine("Enter the new user's username:");

			if(username.size() < 3) throw exception("Please choose a longer username.");

			if(FindAllMatching(
				m_rs.userDB.Begin(), 
				m_rs.userDB.End(), 
				[&username](const User& u){return username == u.GetUsername();}).size() > 0)
				throw exception("There is already a user with that username.");

			password = GetUserLine("Enter the new user's password:");
			if(password.size() < 3) throw exception("Please choose a longer password.");

			cout << "\nEnter 1 for normal user account or 2 for administrator account.\n";

			switch(GetCh())
			{
			case '1':
				m_rs.userDB.AddData(User(username, password, false));
				Alert("Operation Complete.", "Created new user '" + username + "'");
				return;
			case '2':
				m_rs.userDB.AddData(User(username, password, true));
				Alert("Operation Complete.", "Created new administrator '" + username + "'");
				return;
			default:
				Alert("An error has occurred.", "Invalid option. Please choose 1 or 2.");
				continue;
			}
		}
		catch(exception e)
		{
			Alert("An error has occurred.", String(e.what()));
		}
	}
	while(WouldYouLikeToTryAgain("Would you like to make another inquiry?", 
		"Would you like to try again?\nPress 1 to continue or any other key to return to the main menu.\n"));
}

void UserInterface::RemoveUserDialogue()
{
	do
	{
		try
		{
			ClearScreen();
			PrintTitle("Remove User");

			User removeUser = GetUniqueUserFromUserSearch();

			if(removeUser == m_currentUser) throw exception("You are currently logged in as this user. You cannot delete yourself.");

			if(WouldYouLikeToTryAgain("Are you sure you want to delete this user?",
				"Are you sure you want to delete this user?\nPress 1 to continue or any other key to return to the main menu."))
			{
				m_rs.userDB.RemoveData(removeUser);
				Alert("Complete", "The user has been removed from the database.");
			}
			else
				Alert("Not Complete", "The database has not been changed.");

			return;
		}
		catch(exception e)
		{
			Alert("An error has occurred.", String(e.what()));
		}
	}
	while(WouldYouLikeToTryAgain("Would you like to make another inquiry?", "Would you like to try again?\nPress 1 to continue or any other key to return to the main menu.\n"));
}

void UserInterface::AddFlightDialogue()
{
	do
	{
		try
		{
			ClearScreen();
			PrintTitle("Add Flight");

			FlightNumber flightNumber(GetUserLine("Enter flight number [AA123]:"));
			Date date(GetUserLine("Enter takeoff date [DDMMYYYY]:"));
			Time time24(GetUserLine("Enter takeoff time [HHMM]:"));
			Airport source(GetUserLine("Enter source airport code [ABC]:"));
			Airport destination(GetUserLine("Enter destination airport code [ABC]:"));
			unsigned standardSeats(stoi(GetUserLine("Enter the number of seats available:")));

			unsigned firstSeats = standardSeats / 10;

			Flight tentativeFlight(flightNumber, date, time24, source, destination, standardSeats, firstSeats);

			if(m_rs.flightDB.ContainsData(tentativeFlight)) throw exception("Database already contains that flight.");

			m_rs.flightDB.AddData(tentativeFlight);
			Alert("Operation Complete.", "New flight added to the database.\n\nDetails:\n" + PrettyPrintFlight(tentativeFlight));

			return;
		}
		catch(exception e)
		{
			Alert("An error has occurred", "An error has occurred when creating a new Flight: '" + String(e.what()) + "'");
		}

	}
	while(WouldYouLikeToTryAgain("Would you like to try again?", 
		"Would you like to try again? Press 1 to continue or any other key to return to the main menu."));
}

void UserInterface::RemoveFlightDialogue()
{
	do
	{
		try
		{
			// First of all wrangle a unique flight from the user.
			Flight f = GetUniqueFlightFromUserSearch();

			// First remove all Reservations that contained that flight.
			// NB we have to do this first or GetCustomerAndFlightFromReservation will fail!

			// We do this as a 2-step process because if we remove them while iterating over the DB it will
			// bugger up the iterators and sent us straight to hell.
			std::vector<Reservation> reservationRemovalVector;

			if(!WouldYouLikeToTryAgain("Are you sure you want to delete this flight?", 
				"Are you sure you want to delete this flight?\nPress 1 to continue or any other key to return to the main menu."))
				return;

			for_each(
				m_rs.reservationDB.Begin(), 
				m_rs.reservationDB.End(), 
				[&](const Reservation& r) {if(GetCustomerAndFlightFromReservation(r).second == f) reservationRemovalVector.push_back(r);});

			for_each(
				reservationRemovalVector.begin(), 
				reservationRemovalVector.end(), 
				[&](const Reservation& r) {m_rs.reservationDB.RemoveData(r);});

			// Now remove the flight itself from the DB.
			m_rs.flightDB.RemoveData(f);

			// Tell the user what happened.
			Alert("Operation Complete.", "Flight '" + f.GetFlightNumber().AsString() + "' was successfully removed from the database.");
		}
		catch(exception e)
		{
			Alert("An error has occurred", String(e.what()));
		}
	}
	while(WouldYouLikeToTryAgain("Would you like to try again?",
		"Would you like to try again?\nPress 1 to continue or any other key to return to the main menu."));
}

const UserInterface::String UserInterface::PrettyPrintCustomer(const Customer& customer) const
{
	return "Name:\t\t\t" + customer.GetTitle() + " " + customer.GetFirstName() + " " + customer.GetLastName() + "\n"
		+ "Address:\t\t" + customer.GetAddress() + "\n"
		+ "Passport Number:\t" + customer.GetPassportNumber() + "\n"
		+ "Telephone Number:\t" + customer.GetTelephoneNumber() + "\n"
		+ "Unique ID:\t\t" + util::XtoA(customer.GetID());
}

const UserInterface::String UserInterface::PrettyPrintFlight(const Flight& flight) const
{
	return "Flight Number: " + flight.GetFlightNumber().AsString() + "\n"
		+ "Leaving from: " + flight.GetSource().AsString() 
		+ " at " + flight.GetTime().AsString() 
		+ " on " + flight.GetDate().AsString() + "\n"
		+ "Arriving at: " + flight.GetDestination().AsString() + "\n"
		+ "Unique ID: " + util::XtoA(flight.GetID());
}

const UserInterface::String UserInterface::PrettyPrintReservationStatus(const Reservation::RESERVATION_STATUS status) const
{
	switch(status)
	{
	case Reservation::FIRST_CLASS:		return "in a First Class seat.\n";
	case Reservation::STANDARD_CLASS:	return "in a Standard Class seat.\n";
	case Reservation::WAITING_FIRST:	return "on the waiting list for a First Class seat.\n";
	case Reservation::WAITING_STANDARD:	return "on the waiting list for a Standard Class seat.\n";
	default:	throw exception("Reservation has invalid status.");
	}
}

void UserInterface::ClearScreen() const
{
#ifdef _WIN32
	system("cls");
#elif // Must be unix-like
	system("clear");
#endif
}

void UserInterface::PrintTitle(const String& title) const
{
	cout 
	<< "==============================================================================="
	<< "\n"
	<< "\t" << title
	<< "\n"
	<< "==============================================================================="
	<< "\n\n";
}


const char UserInterface::GetCh() const
{
  HANDLE hStdin = GetStdHandle (STD_INPUT_HANDLE);
  INPUT_RECORD irInputRecord;
  DWORD dwEventsRead;
  char cChar;

  while(ReadConsoleInputA (hStdin, &irInputRecord, 1, &dwEventsRead)) /* Read key press */
    if (irInputRecord.EventType == KEY_EVENT
	&&irInputRecord.Event.KeyEvent.wVirtualKeyCode != VK_SHIFT
	&&irInputRecord.Event.KeyEvent.wVirtualKeyCode != VK_MENU
	&&irInputRecord.Event.KeyEvent.wVirtualKeyCode != VK_CONTROL)
    {
		cChar = (char)irInputRecord.Event.KeyEvent.uChar.AsciiChar;
		ReadConsoleInputA (hStdin, &irInputRecord , 1, &dwEventsRead); /* Read key release */
		return cChar;
    }

  return EOF;
}

void UserInterface::Alert(const String& title, const String& message) const
{
	ClearScreen();

	PrintTitle(title);

	cout << message << "\n\nPress any key to continue...\n";

	GetCh();
}

UserInterface::String UserInterface::GetUserLine(const String& prompt) const
{
	String str;
	cout << prompt << endl << "> ";
	getline(cin, str);
	return str;
}

const Customer UserInterface::GetUniqueCustomerFromUserSearch()
{
	String searchData;

	ClearScreen();
	PrintTitle("Customer Search");

	auto userStrings = util::TokeniseString(GetUserLine("Please enter terms to search for a customer."));

	auto searchResults = FindAllMatching(
		m_rs.customerDB.Begin(), 
		m_rs.customerDB.End(), 
		[&userStrings](const Customer& c) { return util::SearchForSubstringMatchesInStrings(util::TokeniseString(c.AsString()), userStrings);});

	if(searchResults.size() == 0) 
	{
		Alert("Search Complete", "No customers were found that matched your query.\nPlease search again.");
	}
	else if(searchResults.size() == 1)
	{
		Alert("Search Complete", "Found unique customer: \n" + PrettyPrintCustomer(*searchResults.back()));
		return *searchResults.back();
	}
	else // searchResults.size() > 1 therefore we need to refine the search.
	{
		String message = "Found " + util::XtoA(searchResults.size()) + " matching records:\n\n";

		for(auto resultsIterator = searchResults.begin(); resultsIterator != searchResults.end(); ++resultsIterator)
			message += PrettyPrintCustomer(**resultsIterator) + "\n\n";

		message += "Please refine your search to a unique customer.";

		Alert("Search complete", message);
		
	}
	throw exception("User did not select a unique customer.");
}

const Flight UserInterface::GetUniqueFlightFromUserSearch()
{
	ClearScreen();
	PrintTitle("Flight Search");

	auto userStrings = util::TokeniseString(GetUserLine("Please enter terms to search for a flight."));

	auto searchResults = FindAllMatching(
		m_rs.flightDB.Begin(), 
		m_rs.flightDB.End(), 
		[&userStrings](const Flight& f) { return util::SearchForSubstringMatchesInStrings(util::TokeniseString(f.AsString()), userStrings);});

	if(searchResults.size() == 0) 
	{
		Alert("Search Complete", "No flights were found that matched your query.\nPlease search again.");
	}
	else if(searchResults.size() == 1)
	{
		Alert("Search Complete", "Found unique flight: \n" + PrettyPrintFlight(*searchResults.back()));
		return *searchResults.back();
	}
	else // searchResults.size() > 1 therefore we need to refine the search.
	{
		String message = "Found " + util::XtoA(searchResults.size()) + " matching records:\n\n";

		for(auto resultsIterator = searchResults.begin(); resultsIterator != searchResults.end(); ++resultsIterator)
			message += PrettyPrintFlight(**resultsIterator) + "\n\n";

		message += "Please refine your search to a unique flight.";

		Alert("Search complete", message);
	}
	throw exception("User did not select a unique flight.");
}

const User UserInterface::GetUniqueUserFromUserSearch()
{
	ClearScreen();
	PrintTitle("User Search");

	auto userLine = util::TokeniseString(GetUserLine("Please enter terms to search for a user."));

	auto searchResults = FindAllMatching(
		m_rs.userDB.Begin(),
		m_rs.userDB.End(),
		[&userLine](const User& u) {return util::SearchForSubstringMatchesInStrings(util::TokeniseString(u.GetUsername()), userLine);});

	if(searchResults.size() == 0) 
	{
		Alert("Search Complete", "No users were found that matched your query.\nPlease search again.");
	}
	else if(searchResults.size() == 1)
	{
		Alert("Search Complete", "Found unique user: \n'" + searchResults.back()->GetUsername() + "'");
		return *searchResults.back();
	}
	else // searchResults.size() > 1 therefore we need to refine the search.
	{
		String message = "Found " + util::XtoA(searchResults.size()) + " matching records:\n\n";

		for(auto resultsIterator = searchResults.begin(); resultsIterator != searchResults.end(); ++resultsIterator)
			message += (**resultsIterator).GetUsername() + "\n\n";

		message += "Please refine your search to a unique user.";

		Alert("Search complete", message);
	}
	throw exception("User did not select a unique user.");
}

const bool UserInterface::WouldYouLikeToTryAgain(const String& title, const String& message) const
{
	ClearScreen();
	PrintTitle(title);

	cout << message << endl;

	return GetCh() == '1';
}

const std::pair<Customer, Flight> UserInterface::GetCustomerAndFlightFromReservation(const Reservation& reservation)
{
		auto matchingFlights = FindAllMatching(
		m_rs.flightDB.Begin(), 
		m_rs.flightDB.End(),
		[&reservation](const Flight& f){return f.GetID() == reservation.GetFlightID();});

		auto matchingCustomers = FindAllMatching(
		m_rs.customerDB.Begin(), 
		m_rs.customerDB.End(),
		[&reservation](const Customer& c){return c.GetID() == reservation.GetCustomerID();});

		if(matchingFlights.size() == 0 || matchingCustomers.size() == 0) throw exception("Could not find both a matching flight and customer.");
		if(matchingFlights.size() > 1 || matchingCustomers.size() > 1) throw exception("Found more than one flight or customer matching the reservation.");

		return std::make_pair(*matchingCustomers[0], *matchingFlights[0]);
}